﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace GoL
{
	public partial class Main : Form
	{
		private Boolean[,] coords;
		private Brush inactiveBrush = new SolidBrush(Color.Gray);
		private Brush activeBrush = new SolidBrush(Color.Yellow);
		private Boolean GridIsResized = false;
		private Point gridDimension = new Point(30, 30);
		private Int32 cellSize = 10;
		private Int32 cellSpacing = 1;
		private Int32 animateInterval = 80;
		private Timer timer;
		private Bitmap offScreenBmp;
		private Int32 leftOffset = 10; // initial left and top offset prevent the grid from sticking to the window border
		private Int32 topOffset = 30;

		public Main()
		{
			InitializeComponent();
			coords = new Boolean[gridDimension.X, gridDimension.Y];
			timer = new Timer();
			timer.Interval = animateInterval;
			timer.Tick += new EventHandler(timer_Tick);
			RedrawField();
		}

		private void timer_Tick(object sender, EventArgs e)
		{
			Boolean[,] newCoords = (Boolean[,])coords.Clone(); // duplicate the grid to save the result to
			for (Int32 column = 0; column < gridDimension.Y; column++)
			{
				for (Int32 line = 0; line < gridDimension.X; line++) // we check for all neighbours below
				{
					Int32 neighbours = 0;
					if (line != 0 && column != 0)
					{
						if (coords[line - 1, column - 1] == true) // top left
							neighbours++;
					}
					if (column != 0)
					{
						if (coords[line, column - 1] == true) // top center
							neighbours++;
					}
					if (line != gridDimension.X - 1 && column != 0)
					{
						if (coords[line + 1, column - 1] == true) // top right
							neighbours++;
					}
					if (line != gridDimension.X - 1)
					{
						if (coords[line + 1, column] == true) // middle right
							neighbours++;
					}
					if (line != gridDimension.X - 1 && column != gridDimension.Y - 1)
					{
						if (coords[line + 1, column + 1] == true) // bottom right
							neighbours++;
					}
					if (column != gridDimension.Y - 1)
					{
						if (coords[line, column + 1] == true) // bottom center
							neighbours++;
					}
					if (line != 0 && column != gridDimension.Y - 1)
					{
						if (coords[line - 1, column + 1] == true) // bottom left
							neighbours++;
					}
					if (line != 0)
					{
						if (coords[line - 1, column] == true) // middle left
							neighbours++;
					}
					// a cell with more than 3 or less than 2 will die
					if (neighbours > 3 || neighbours < 2)
					{
						newCoords[line, column] = false;
					}
					// a cell with exactly 3 living neighbours comes to life
					if (neighbours == 3)
					{
						newCoords[line, column] = true;
					}
				}
			}
			coords = (Boolean[,])newCoords.Clone(); // save the duplicated grid with the results back to the original grid
			RedrawField();
			this.Invalidate();
		}

		private void RedrawField() // field only gets redrawn when needed
		{
			if (!GridIsResized) // match the window size to the gridsize
			{
				this.Width = 22 + (cellSize * gridDimension.X) + (gridDimension.X * cellSpacing) + leftOffset;
				this.Height = 40 + (cellSize * gridDimension.Y) + (gridDimension.Y * cellSpacing) + topOffset;
				GridIsResized = true;
				offScreenBmp = new Bitmap(this.Width, this.Height);
			}
			Graphics offScreenGr; // grid gets rendered to an offscreen buffer
			offScreenGr = Graphics.FromImage(offScreenBmp);
			Int32 locationX = leftOffset;
			Int32 locationY = topOffset;
			for (Int32 column = 0; column < gridDimension.Y; column++)
			{
				for (Int32 line = 0; line < gridDimension.X; line++) // iterate through each cell. True =  alive, False = dead
				{
					if (coords[line, column] == false)
					{
						offScreenGr.FillRectangle(inactiveBrush, locationX, locationY, cellSize, cellSize);
					}
					else
					{
						offScreenGr.FillRectangle(activeBrush, locationX, locationY, cellSize, cellSize);
					}
					locationX += cellSpacing + cellSize;
				}
				locationX = leftOffset;
				locationY += cellSpacing + cellSize;
			}
		}

		private void RecalculateGrid() // need to call this when the gridsize changes
		{
			Boolean[,] newCoords = new Boolean[gridDimension.X, gridDimension.Y];
			for (Int32 column = 0; column < gridDimension.Y; column++)
			{
				for (Int32 line = 0; line < gridDimension.X; line++)
				{
					if (line < coords.GetLength(0) && column < coords.GetLength(1)) // check if the current coordinates existed in the previous grid
					{
						newCoords[line, column] = coords[line, column];
					}
				}
			}
			coords = (Boolean[,])newCoords.Clone();
		}

		protected override void OnPaint(PaintEventArgs e)
		{
			Graphics gfs = e.Graphics;
			gfs.DrawImage(offScreenBmp, 0, 0); // render offscreen buffer to screen (enable double buffering for flicker free result)
			base.OnPaint(e);
		}

		private void tbnStart_Click(object sender, EventArgs e)
		{
			if (tbnStart.Tag.ToString() == "START")
			{
				timer.Start();
				tbnStart.Text = "Stop";
				tbnStart.Tag = "STOP";
				tbnStart.ImageIndex = 1;
			}
			else
			{
				timer.Stop();
				tbnStart.Text = "Start";
				tbnStart.Tag = "START";
				tbnStart.ImageIndex = 0;
			}
		}

		private void Main_MouseClick(object sender, MouseEventArgs e)
		{
			Point location = e.Location;
			// determine wich cell was clicked
			Int32 pointX = ((location.X - leftOffset) / (cellSize + cellSpacing));
			Int32 pointY = ((location.Y - topOffset) / (cellSize + cellSpacing));
			coords[pointX, pointY] = !coords[pointX, pointY]; // change the state of the clicked cell
			RedrawField();
			this.Invalidate();
		}

		private void tbnSettings_Click(object sender, EventArgs e)
		{
			SettingsMenu settings = new SettingsMenu(gridDimension, cellSize, cellSpacing, animateInterval, ((SolidBrush)inactiveBrush).Color, ((SolidBrush)activeBrush).Color);
			settings.ShowDialog();
			if (settings.changed) // apply all changes if setting were altered
			{
				this.gridDimension = settings.gridDimension;
				this.cellSize = settings.cellSize;
				this.cellSpacing = settings.cellSpacing;
				this.animateInterval = settings.animateInterval;
				this.inactiveBrush = new SolidBrush(settings.inactiveColor);
				this.activeBrush = new SolidBrush(settings.activeColor);
				timer.Interval = animateInterval;
				GridIsResized = false;
				RecalculateGrid();
				RedrawField();
				this.Invalidate();
			}
		}
	}
}
